#    Rubatom
#    Copyright (c) 2014 Sebastian Dufner
#
#    This file is part of Rubatom.
#
#    Rubatom is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Rubatom is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.

require 'rubatom/classify/ship_classifier_methods'
require 'rubatom/classify/score'
require 'rubatom/versioned_algorithm'

module Rubatom

  ##
  # Basic library class for ship classifications.
  #
  # == API Notes ==
  # You probably won't need to override any of the methods defined in this
  # class. See #affiliation, #ship_type, #classifier, #version.

  class ShipClassifier
    include ShipClassifierMethods
    include VersionedAlgorithm
    version 0.1

    def self.const_missing(name); name; end
    def self.method_missing(name, *args)
      "#{name} #{args.join ' '}".to_sym
    end

    def self.classifier(*args)
      classifier = self.ship_classifier(*args)
      $logger.info "Choosing classifier #{classifier}" if $logger
      classifier
    end

    ##
    # Defines a ship type (like *Fighter*). On #classification, the score
    # functions will be evaluated and the ship type with the highest score will
    # be chosen.
    #
    # === Parameters ===
    # [+type+]  The ship type (Symbol or String).
    # [+block+] The score function. It uses the instance methods of this class
    #           to compute a score value in 0..1.
    #
    # == API Notes ==
    #
    #
    # :call-seq: ship_type(type) { ... }

    def self.ship_type(type, &score_function)
      (@ship_types ||= {})[type] = Score.new &score_function
    end
    private_class_method :ship_type

    ##
    # Returns all ship types, along with their score functions, defined in this
    # class and in all of its superclasses.

    def self.ship_types
      @ship_types ||= {}

      return @ship_types unless superclass.respond_to? :ship_types
      superclass.ship_types.merge @ship_types
    end

    ##
    # Defines an affiliation (like *Military*). Affiliations can be used to
    # solve conflicts in ship type scores.
    #
    # === Parameters ===
    # [+type+]  The affiliation (Symbol or String).
    # [+block+] The score function. It uses the instance methods of this class
    #           to compute a score value in 0..1.
    #
    # == API Notes ==
    #
    # :call-seq: affiliation(affiliation) { ... }

    def self.affiliation(affiliation, &score_function)
      (@affiliations ||= {})[affiliation] = Score.new &score_function
    end
    private_class_method :affiliation

    def self.affiliations
      @affiliations ||= {}

      return @affiliations unless superclass.respond_to? :affiliations
      superclass.affiliations.merge @affiliations
    end

    def initialize(structure, **options)
      @structure, @options = structure, options
    end

    def affiliation_score(affiliation)
      affiliation_scores[affiliation]
    end

    def affiliation_scores
      @affiliation_scores ||=
          Hash[*self.class.affiliations
                .map {|a, f| [a, instance_exec(&f)]}.flatten]
    end

    def chassis_id
      # Essentially, the number of adjacent free spaces of every hull block
      # determines our score
      x_sum2, x_sum, x_num = chassis_scores chassis_projection_x
      y_sum2, y_sum, y_num = chassis_scores chassis_projection_y
      z_sum2, z_sum, z_num = chassis_scores chassis_projection_z

      ((x_num ^ y_num ^ z_num) / 15 + 2 & -2) ** 2 * 5 +
          (x_sum2 ^ y_sum2 ^ z_sum2) / ((x_sum ^ y_sum ^ z_sum) + 1)
    end

    def chassis_series
      # The same as with chassis_id, but the chassis will be scaled down
      # beforehand.
      x_sum2, x_sum, x_num = chassis_series_scores chassis_projection_x
      y_sum2, y_sum, y_num = chassis_series_scores chassis_projection_y
      z_sum2, z_sum, z_num = chassis_series_scores chassis_projection_z

      score = (x_sum ^ y_sum ^ z_sum) +
          (x_sum2 ^ y_sum2 ^ z_sum2) ^ (x_num ^ y_num ^ z_num)

      chassis_series_string(
            chassis_series_components, score)
    end

    def classification
      "#{chassis_series}-#{chassis_id} #{ship_type}"
    end

    ##
    # Determines the type of the ship.
    #
    # _Note:_ You probably won't need to override this, see #self.ship_type for
    # details.

    def ship_type
      @ship_type ||= ship_type_scores.max_by(&:first)[1]
    end

    def ship_type_scores
      @ship_type_scores ||=
          self.class.ship_types.map {|t, f| [instance_exec(&f), t]}
    end

    def debug
      if $logger
        $logger.debug do
          message =      "amc         #{amc_score}\n"
          message.concat "chassis id  #{chassis_id}\n"
          message.concat "chassis se  #{chassis_series}\n"
          message.concat "d1000       #{d1000_score}\n"
          message.concat "disint      #{disintegrator_score}\n"
          message.concat "docking     #{docking_score}\n"
          message.concat "drain       #{powerdrain_score}\n"
          message.concat "engines     #{engine_score}\n"
          message.concat "height      #{height}\n"
          message.concat "length      #{length}\n"
          message.concat "mass        #{mass}\n"
          message.concat "pulse       #{pulse_score}\n"
          message.concat "repair      #{repair_score}\n"
          message.concat "salvage     #{salvage_score}\n"
          message.concat "sdbb        #{sdbb_score}\n"
          message.concat "sdkb        #{sdkb_score}\n"
          message.concat "shape       #{shape_score}\n"
          message.concat "shields     #{shield_score}\n"
          message.concat "stealth     #{stealth_score}\n"
          message.concat "storage     #{storage_score}\n"
          message.concat "supply      #{powersupply_score}\n"
          message.concat "turrets     #{turret_score}\n"
          message.concat "utility     #{utility_score}\n"
          message.concat "weapons     #{weapon_score}\n"
          message.concat "width       #{width}\n"
          message.concat "\n"
          affiliation_scores.map {|a, s| [s, a]}.sort.reverse.each do |s, a|
            message.concat "affiliation #{a} #{s}\n"
          end
          ship_type_scores.sort.reverse.each do |s, t|
            message.concat "type        #{t} #{s}\n"
          end

          message
        end
      end

      nil
    end
  end

  class ShipClassifier_v0_2 < ShipClassifier
    version 0.2

    ship_type Fighter do
      affiliation Military
      max_dim 20, 13
    end
    ship_type Heavy Fighter do
      affiliation Military
      max_dim 50, 17
    end
    ship_type Corvette do
      affiliation Military
      max_dim 100, 33
    end
    ship_type Frigate do
      affiliation Military
      max_dim 200, 67
    end
    ship_type Cruiser do
      affiliation Military
      max_dim 333, 67
    end
    ship_type Heavy Cruiser do
      affiliation Military
      max_dim 533, 133
    end
    ship_type Battlecruiser do
      affiliation Military
      max_dim 800, 133
    end
    ship_type Dreadnought do
      affiliation Military
      max_dim 1200, 267
    end

    ship_type Shuttle do
      affiliation Civilian
      max_dim 30, 12
    end
    ship_type Light Transport do
      affiliation Civilian
      max_dim 60, 18
    end
    ship_type Medium Transport do
      affiliation Civilian
      max_dim 100, 22
    end
    ship_type Heavy Transport do
      affiliation Civilian
      max_dim 164, 42
    end

    ship_type Raider do
      affiliation Outlaw
      max_dim 18, 6
    end
    ship_type Light Freighter do
      affiliation Outlaw
      max_dim 35, 11
    end
    ship_type Heavy Raider do
      affiliation Outlaw
      max_dim 58, 12
    end
    ship_type Destroyer do
      affiliation Outlaw
      max_dim 85, 15
    end

    affiliation Military do
      shape_score 1, 0.3
      turret_score > [1, 0.2]
      weapon_score > [1, 0.2]
    end
    affiliation Civilian do
      shape_score 0.4, 0.1
      turret_score < [0.2, 0.1]
      weapon_score < [0.2, 0.1]
    end
    affiliation Outlaw do
      shape_score 0.6, 0.1
      turret_score 0.55, 0.25
      weapon_score 0.55, 0.25
    end
  end

  class ShipClassifier_v0_2_1 < ShipClassifier_v0_2
    version '0.2.1'

    def turret_enhancer_multiplier; 30; end

    ship_type Fighter do
      affiliation Military
      max_dim < [20, 13]
    end
    ship_type Shuttle do
      affiliation Civilian
      max_dim < [30, 12]
    end
    ship_type Raider do
      affiliation Outlaw
      max_dim < [18, 6]
    end

    affiliation Military do
      shape_score > [0.8, 0.1]
      offense_score > [1.5, 0.3]
    end
    affiliation Outlaw do
      shape_score 0.6, 0.1
      offense_score 0.825, 0.375
    end
    affiliation Civilian do
      shape_score 0.4, 0.1
      offense_score < [0.3, 0.15]
    end
  end
end
